home *** CD-ROM | disk | FTP | other *** search
/ CD ROM Paradise Collection 4 / CD ROM Paradise Collection 4 1995 Nov.iso / system / 4utils84.zip / 4desc.pas < prev    next >
Pascal/Delphi Source File  |  1994-10-08  |  37KB  |  1,078 lines

  1. PROGRAM FileDescEditor;
  2. {$A+,B-,D-,E-,F-,G+,L+,N-,O-,R+,S+,V-,X-}
  3. {$M 8192,0,655360}
  4.  
  5. (* ----------------------------------------------------------------------
  6.    A Simple 4DOS File Description Editor
  7.  
  8.    (c) 1992, 1993 Copyright by
  9.  
  10.        David Frey,         & Tom Bowden
  11.        Urdorferstrasse 30    1575 Canberra Drive
  12.        8952 Schlieren ZH     Stone Mountain, GA 30088-3629
  13.        Switzerland           USA
  14.  
  15.        Code created using Turbo Pascal 7.0, (c) Borland International 1992
  16.  
  17.    DISCLAIMER: This program is freeware: you are allowed to use, copy
  18.                and change it free of charge, but you may not sell or hire
  19.                4DESC. The copyright remains in our hands.
  20.  
  21.                If you make any (considerable) changes to the source code,
  22.                please let us know. (send a copy or a listing).
  23.                We would like to see what you have done.
  24.  
  25.                We, David Frey and Tom Bowden, the authors, provide absolutely
  26.                no warranty of any kind. The user of this software takes the
  27.                entire risk of damages, failures, data losses or other
  28.                incidents.
  29.  
  30.    ----------------------------------------------------------------------- *)
  31.  
  32. USES {$IFOPT G+} Test286, {$ENDIF}
  33.      Fix, Crt, Dos, Memory, Drivers,
  34.      StringDateHandling, DisplayKeyboardAndCursor, HandleINIFile,
  35.      DescriptionHandling, dmouse;
  36.  
  37. CONST DelimiterTable : STRING = ',.();:-!?/[]{}+*=''`"@%&$_£';
  38.  
  39. VAR  EdStart     : BYTE;      (* column where the description starts     *)
  40.  
  41.      ActDir      : DirStr;    (* current directory                       *)
  42.      StartDir    : DirStr;    (* directory where we started from         *)
  43.      ResetDir    : BOOLEAN;   (* TRUE = return to StartDir on exit       *)
  44.  
  45.      StartIndex  : INTEGER;   (* index of entry at the top of the screen *)
  46.      Index       : INTEGER;   (* index of entry we are editing           *)
  47.  
  48.      CutPasteDesc: DescStr;   (* cut, resp. pasted description           *)
  49.      Changed     : BOOLEAN;   (* TRUE=the descriptions have been edited  *)
  50.      IORes       : INTEGER;
  51.  
  52.      NewDir      : DirStr;    (* temporary storage for a directory path, *)
  53.      NewName     : NameExtStr;(* used by view and others                 *)
  54.  
  55.      FirstParam  : STRING[8];
  56.      i           : BYTE;      (* variable for counting (index etc)       *)
  57.      ShowHelp    : BOOLEAN;   (* TRUE = start in help mode [/h]          *)
  58.      Querier     : BOOLEAN;   (* TRUE = ask user if he wants to save
  59.                                         the descriptions   [/dontask]    *)
  60.      PasteMovesToNextIndex: BOOLEAN; (* TRUE = Paste advances to next index *)
  61.      Overwrite   : BOOLEAN;   (* overwrite / Insert mode                 *)
  62.  
  63.      s           : STRING;    (* temporary string variable               *)
  64.  
  65. (*-------------------------------------------------------- Display-Routines *)
  66. PROCEDURE DisplayFileEntry(Index: INTEGER; ox, x: BYTE;
  67.                            Selection, Hilighted: BOOLEAN);
  68. (* Displays the Index'th file entry. If the description is longer than
  69.    DispLen characters, DispLen characters - starting at character x of the
  70.    description - will be shown. (this feature is needed for scrolling).
  71.    Hilighted = TRUE will hilight the description.
  72.  
  73.    When Selection is TRUE, we want to display the text we just put into
  74.    the buffer, ox (old x) gives us the start of the selection.
  75.  
  76.    P.S. Scrolling implies hilighting, but this fact has not been exploited. *)
  77.  
  78.  VAR FileEntry : PFileData;
  79.      xs,oxs,t  : BYTE;
  80.      y,l       : BYTE;
  81.      s         : STRING;
  82.  
  83.  BEGIN
  84.   y := 3+Index-StartIndex;
  85.   IF (Index >= 0) AND (Index < FileList^.Count) THEN
  86.    BEGIN
  87.     FileEntry := NILCheck(FileList^.At(Index));
  88.  
  89.     IF x <=   DispLen THEN xs := 1
  90.     ELSE
  91.     IF x <= 2*DispLen THEN xs := DispLen+1
  92.     ELSE
  93.     IF x <= 3*DispLen THEN xs := 2*DispLen+1
  94.     ELSE
  95.     IF x <= 4*DispLen THEN xs := 3*DispLen+1
  96.                       ELSE xs := 4*DispLen+1;
  97.     (* I haven't found a simple formula yet, so I'm doing the
  98.        job with a table. That's the lazy's man solution .... *)
  99.  
  100.     IF ox <=   DispLen THEN oxs := 1
  101.     ELSE
  102.     IF ox <= 2*DispLen THEN oxs := DispLen+1
  103.     ELSE
  104.     IF ox <= 3*DispLen THEN oxs := 2*DispLen+1
  105.     ELSE
  106.     IF ox <= 4*DispLen THEN oxs := 3*DispLen+1
  107.                        ELSE oxs := 4*DispLen+1;
  108.  
  109.     IF Hilighted THEN
  110.      BEGIN TextColor(SelectFg); TextBackGround(SelectBg); END
  111.     ELSE
  112.      BEGIN
  113.       TextBackGround(NormBg);
  114.  
  115.       IF FileEntry^.IsADir THEN TextColor(DirFg)
  116.                            ELSE TextColor(NormFg)
  117.      END;
  118.  
  119.     GotoXY(1,y);
  120.  
  121.     s := FileEntry^.FormatScrollableDescription(xs,DispLen);
  122.     IF Selection THEN
  123.      BEGIN
  124.        IF ox > x  THEN BEGIN t := x; x := ox; ox := t; END
  125.                   ELSE t := x;
  126.        IF ox < xs THEN ox := xs;
  127.  
  128.        Write(Copy(s,1,EdStart+ox-xs-1));
  129.        TextBackGround(NormFg);  TextColor(NormBg);   Write(Copy(s,EdStart+ox-xs,x-ox));
  130.        TextBackGround(SelectBg);TextColor(SelectFg); Write(Copy(s,EdStart+x-xs,255));
  131.  
  132.        x := t;
  133.      END
  134.     ELSE Write(s);
  135.  
  136.     l := Length(FileEntry^.GetDesc);
  137.     IF l-xs < DispLen THEN
  138.      ClrEol
  139.     ELSE
  140.      BEGIN
  141.       TextColor(WarnFg); Write(Chr(16)); TextColor(NormFg);
  142.      END;
  143.  
  144. (*    IF x <= DispLen THEN GotoXY(EdStart+x-1,y)
  145.                     ELSE GotoXY(EdStart+DispLen-1,y) *)
  146.     GotoXY(EdStart+x-xs,y);
  147.    END
  148.   ELSE BEGIN GotoXY(1,y); ClrEol; END;
  149.  END;  (* DisplayFileEntry *)
  150.  
  151. PROCEDURE DrawDirLine(UpdateDir: BOOLEAN);
  152. (* Draw the line, which tells us where in the directory tree we are. *)
  153.  
  154. BEGIN
  155.  IF UpdateDir THEN
  156.   BEGIN
  157.    GetDir(0,ActDir);
  158.    IF ActDir[Length(ActDir)] <> '\' THEN ActDir := ActDir + '\';
  159.    UpString(ActDir);
  160.   END;
  161.  TextColor(DirFg); TextBackGround(NormBg);
  162.  GotoXY(1,2); Write(ActDir); ClrEol;
  163. END; (* DrawDirLine *)
  164.  
  165. PROCEDURE ReDrawScreen;
  166. (* Redraws the full screen, needed after shelling out or after printing
  167.    the help screen.                                                     *)
  168.  
  169. VAR Index: INTEGER;
  170.  
  171. BEGIN
  172. (* GetDir(0,ActDir); *)
  173.  FOR Index := StartIndex TO StartIndex+MaxLines-4 DO
  174.   DisplayFileEntry(Index,0,1,FALSE,FALSE);
  175. END; (* ReDrawScreen *)
  176.  
  177.  
  178. (*-------------------------------------------------------- Read-Directory *)
  179. PROCEDURE ReadFiles;
  180. (* Scan the current directory and read in the DESCRIPT.ION file. Build a
  181.    file list database and associate the right description.
  182.  
  183.    Warn the user if there are too long descriptions or if there are too
  184.    much descriptions.                                                     *)
  185.  
  186. VAR i   : BYTE;
  187.     ch  : WORD;
  188.     Dir : PathStr;
  189.  
  190. BEGIN
  191.  Changed    := FALSE;
  192.  DescLong   := FALSE;
  193.  Index      := 0;
  194.  StartIndex := 0;
  195.  Dir := FExpand('.');
  196.  
  197.  IF FileList <> NIL THEN
  198.   BEGIN
  199.    Dispose(FileList,Done); FileList := NIL;
  200.   END;
  201.  
  202.  TextColor(StatusFg); TextBackGround(StatusBg);
  203.  GotoXY(1,MaxLines);
  204.  Write(Chars(' ',((ScreenWidth-40-Length(Dir)) DIV 2)),
  205.        'Scanning directory ',Dir,' .....  please wait.');
  206.  ClrEol;
  207.  
  208.  FileList := NIL; FileList := New(PFileList,Init(Dir,'*.*',0));
  209.  IF FileList = NIL THEN Abort('Unable to allocate FileList');
  210.  
  211.  IF (FileList^.Status = ListTooManyFiles) OR
  212.     (FileList^.Status = ListOutofMem) THEN
  213.   BEGIN
  214.    TextColor(NormFg); TextBackGround(NormBg);
  215.    FOR i := 3 TO MaxLines-1 DO
  216.     BEGIN
  217.      GotoXY(1,i); ClrEol;
  218.     END;
  219.    IF FileList^.Status = ListTooManyFiles THEN
  220.     ReportError('Warning! Too many files in directory, description file will be truncated! (Key)',(CutPasteDesc <> ''),Changed)
  221.    ELSE
  222.     ReportError('Warning! Out of memory, description file will be truncated! (Key)',(CutPasteDesc <> ''),Changed);
  223.   END;
  224.  
  225.  IF FileList^.Count > 0 THEN
  226.   BEGIN
  227.    DrawMainScreen(Index,FileList^.Count,1,0); DrawDirLine(TRUE);
  228.   END;
  229.  
  230.  IF DescLong THEN
  231.   BEGIN
  232.    TextColor(NormFg); TextBackGround(NormBg);
  233.    FOR i := 3 TO MaxLines-1 DO
  234.     BEGIN
  235.      GotoXY(1,i); ClrEol;
  236.     END;
  237.    ReportError('Warning! Some descriptions are too long; they will be truncated. Press any key.',(CutPasteDesc <> ''),Changed);
  238.   END;
  239. END;  (* ReadFiles *)
  240.  
  241. (*-------------------------------------------------------- Save Descriptions *)
  242. PROCEDURE SaveDescriptions;
  243. (* Save the modified descriptions currently held in memory onto disk.
  244.    Rename the old description file into DESCRIPT.OLD and write the
  245.    new one out. Any problems occuring at this point (disk full etc),
  246.    raise a warning message and cause a deletion of the (half-written)
  247.    description file DESCRIPT.ION. In this case the user "only" looses his
  248.    new, edited descriptions, but the old ones are stored in the DESCRIPT.OLD
  249.    file and can be restored by typing
  250.  
  251.    REN DESCRIPT.OLD DESCRIPT.ION
  252.    ATTRIB +H DESCRIPT.ION
  253.  
  254.    If all went fine, the old description file gets deleted. This procedure
  255.    minimizes data loss.                                                    *)
  256.  
  257. VAR DescFile  : TEXT;
  258.     DescSaved : BOOLEAN;
  259.     Time      : DateTime;
  260.     ch        : WORD;
  261.     FileEntry : PFileData;
  262.  
  263.  
  264.  PROCEDURE SaveEntry(FileEntry: PFileData); FAR;
  265.  (* Save a single description, writes a single line of the description
  266.     file. This procedures is called for each entry in the FileEntry list *)
  267.  
  268.  VAR Desc     : DescStr;
  269.      ProgInfo : STRING;
  270.      Dir      : DirStr;
  271.      BaseName : NameStr;
  272.      Ext      : ExtStr;
  273.  
  274.  BEGIN
  275.   Desc := FileEntry^.GetDesc;
  276.   StripLeadingSpaces(Desc); StripTrailingSpaces(Desc);
  277.   IF Desc <> '' THEN
  278.    BEGIN
  279.     StripTrailingSpaces(FileEntry^.Name);
  280.     Write(DescFile,FileEntry^.Name);
  281.  
  282.     StripLeadingSpaces(FileEntry^.Ext);
  283.     StripTrailingSpaces(FileEntry^.Ext);
  284.     IF FileEntry^.Ext <> '' THEN Write(DescFile,FileEntry^.Ext);
  285.  
  286.     Write(DescFile,' ',Desc);
  287.     IF DescSaved = FALSE THEN DescSaved := TRUE;
  288.  
  289.     ProgInfo :=  FileEntry^.GetProgInfo;
  290.     IF ProgInfo <> '' THEN Write(DescFile,ProgInfo);
  291.     WriteLn(DescFile);
  292.    END;
  293.  END; (* SaveEntry *)
  294.  
  295. BEGIN
  296.  DescSaved := FALSE;
  297.  IF DiskFree(0) < FileList^.Count*SizeOf(TFileData) THEN
  298.    ReportError('Probably out of disk space. Nevertheless trying to save DESCRIPT.ION...',(CutPasteDesc <> ''),Changed);
  299.  TextColor(StatusFg); TextBackGround(StatusBg);
  300.  GotoXY(1,MaxLines);
  301.  Write(Chars(' ',((ScreenWidth-41) div 2)),
  302.        'Saving descriptions........  please wait.');
  303.  ClrEol;
  304.  
  305.  {$I-}
  306.  Assign(DescFile,'DESCRIPT.ION'); Rename(DescFile,'DESCRIPT.OLD'); IORes := IOResult;
  307.  Assign(DescFile,'DESCRIPT.ION'); SetFAttr(DescFile,Archive); IORes := IOResult;
  308.  Rewrite(DescFile);
  309.  {$I+}
  310.  IF IOResult > 0 THEN
  311.   BEGIN
  312.    ReportError('Unable to write DESCRIPT.ION !',(CutPasteDesc <> ''),Changed);
  313.    {$I-}
  314.    Assign(DescFile,'DESCRIPT.OLD'); Rename(DescFile,'DESCRIPT.ION'); IORes := IOResult;
  315.    {$I+}
  316.   END
  317.  ELSE
  318.   BEGIN
  319.    FileList^.ForEach(@SaveEntry);
  320.    {$I-}
  321.    Close(DescFile);
  322.    {$I+}
  323.  
  324.    IF IOResult > 0 THEN
  325.     BEGIN
  326.      ReportError('Unable to write DESCRIPT.ION !',(CutPasteDesc <> ''),Changed);
  327.      {$I-}
  328.      Assign(DescFile,'DESCRIPT.OLD'); Rename(DescFile,'DESCRIPT.ION'); IORes := IOResult;
  329.      {$I+}
  330.     END
  331.    ELSE
  332.     BEGIN
  333.      IF DescSaved THEN SetFAttr(DescFile, Archive + Hidden)
  334.                   ELSE Erase(DescFile);  (* Don't keep zero-byte file. *)
  335.      Changed := FALSE; DrawStatusLine(TRUE,(CutPasteDesc <> ''),Changed,FALSE);
  336.      {$I-}
  337.      Assign(DescFile,'DESCRIPT.OLD'); Erase(DescFile); IORes := IOResult;
  338.      {$I+}
  339.     END;
  340.   END;
  341. END;  (* SaveDescriptions *)
  342.  
  343. (*-------------------------------------------------------- Edit Descriptions *)
  344. PROCEDURE EditDescriptions;
  345. (* This is the heart of 4DESC: the editing of the descriptions. *)
  346.  
  347. VAR Key          : WORD;
  348.     Drv          : STRING[3];
  349.     LastDrv      : CHAR;
  350.     x,y,l        : BYTE;        (* current cursor position *)
  351.     ox           : BYTE;        (* old cursor position *)
  352.     EditStr      : DescStr;
  353.     InShiftState : BOOLEAN;
  354.     Cmd          : BYTE;
  355.  
  356.     Cursor       : WORD;
  357.     OldDir       : DirStr;
  358.     ActFileData  : PFileData;
  359.     n            : NameExtStr;
  360.  
  361.     ReverseFlag  : BOOLEAN;   (* for sorting *)
  362.  
  363.  PROCEDURE UpdateLineNum(Index: INTEGER);
  364.  (* Update the line number indicator in the right corner and redraw
  365.     the associated description line                                 *)
  366.  
  367.  BEGIN
  368.   TextColor(StatusFg); TextBackGround(StatusBg);
  369.   GotoXY(46,1); Write(Index+1:5);
  370.  
  371.   IF Changed THEN DrawStatusLine(FALSE,(CutPasteDesc <> ''),Changed,ReverseFlag);
  372.  
  373.   IF Index < FileList^.Count THEN
  374.    BEGIN
  375.     EditStr := PFileData(FileList^.At(Index))^.GetDesc;
  376.     DisplayFileEntry(Index,0,1,FALSE,TRUE);
  377.    END;
  378.  
  379.   ActFileData := NILCheck(FileList^.At(Index));
  380.  END;
  381.  
  382.  PROCEDURE UpdateColNum(Col, CurDescLen: BYTE);
  383.  (* Update the column number indicator in the right corner *)
  384.  
  385.  VAR x,y: BYTE;
  386.  
  387.  BEGIN
  388.   x := WhereX; y := WhereY;
  389.   TextColor(StatusFg); TextBackGround(StatusBg);
  390.   GotoXY(66,1); Write(Col:3); GotoXY(77,1); Write(CurDescLen:3);
  391.  
  392. (*  TextBackGround(NormBg);
  393.     GotoXY(EdStart+40-xs,MaxLines); Write('^'); *)
  394.  
  395.   GotoXY(x,y);
  396.  END;
  397.  
  398.  PROCEDURE PrevIndex(VAR Index: INTEGER);
  399.  (* Go up one description line (if possible) *)
  400.  
  401.  BEGIN
  402.   Index := Max(Index-1,0);
  403.   IF Index <= StartIndex THEN
  404.    BEGIN
  405.     StartIndex := Max(Index-ScreenSize,Index);
  406.     RedrawScreen;
  407.    END;
  408.   UpdateLineNum(Index);
  409.  END; (* PrevIndex *)
  410.  
  411.  PROCEDURE NextIndex(VAR Index: INTEGER);
  412.  (* Go down one description line (if possible) *)
  413.  
  414.  BEGIN
  415.   Index := Min(Index+1,FileList^.Count-1);
  416.   IF Index > StartIndex+ScreenSize THEN
  417.    BEGIN
  418.     StartIndex := Index-ScreenSize;
  419.     RedrawScreen;
  420.    END;
  421.   UpdateLineNum(Index);
  422.  END; (* NextIndex *)
  423.  
  424.  PROCEDURE QuerySaveDescriptions;
  425.  (* Ask the user if he really wants to save the descriptions. *)
  426.  
  427.  VAR ch: CHAR;
  428.  
  429.  BEGIN
  430.   IF Querier THEN
  431.    BEGIN
  432.     TextColor(StatusFg); TextBackGround(StatusBg);
  433.     IF Changed THEN
  434.      BEGIN
  435.       GotoXY(1,MaxLines);
  436.       Write(Chars(' ',(ScreenWidth-58) div 2),
  437.            'Descriptions have been edited. Shall they be saved (Y/N) ?');
  438.       ClrEol;
  439.       ch := ' ';
  440.       REPEAT
  441.         If KeyPressed Then ch := UpCase(ReadKey)
  442.         Else
  443.           If MouseLoaded Then
  444.             Begin
  445.               ButtonReleased(Left);
  446.               If ReleaseCount > 0 Then ch := 'Y';
  447.               ButtonReleased(Right);
  448.               If ReleaseCount > 0 Then ch := 'N';
  449.             End;
  450.       UNTIL (ch = 'Y') OR (ch = 'N');
  451.       Write(' ',ch);
  452.       IF ch = 'Y' THEN SaveDescriptions;
  453.      END;
  454.    END
  455.   ELSE SaveDescriptions; (* always save, when not in query mode *)
  456.  END; (* QuerySaveDescriptions *)
  457.  
  458.  PROCEDURE DirUp;
  459.  (* Go up one directory in the directory tree (if possible) *)
  460.  
  461.  BEGIN
  462.   IF Changed THEN QuerySaveDescriptions;
  463.   {$I-}
  464.   ChDir('..');
  465.   {$I+}
  466.   IF IOResult = 0 THEN
  467.    BEGIN
  468.     ReadFiles;
  469.     RedrawScreen;
  470.     DrawStatusLine(TRUE,(CutPasteDesc <> ''),Changed,ReverseFlag);
  471.     Index := 0; UpdateLineNum(Index);
  472.    END;
  473.  END;  (* DirUp *)
  474.  
  475.  PROCEDURE DirDown;
  476.  (* Go down one directory in the directory tree (if possible) *)
  477.  
  478.  BEGIN
  479.   IF (Index < FileList^.Count) THEN
  480.    BEGIN
  481.     n  := ActFileData^.Name+ActFileData^.Ext;
  482.     IF (ActFileData^.IsADir) AND (n[1] <> '.') THEN
  483.      BEGIN
  484.       IF Changed THEN QuerySaveDescriptions;
  485.       {$I-}
  486.       ChDir(n);
  487.       {$I+}
  488.       IF IOResult = 0 THEN
  489.        BEGIN
  490.         ReadFiles;
  491.         RedrawScreen;
  492.        END;
  493.       DrawStatusLine(TRUE,(CutPasteDesc <> ''),Changed, ReverseFlag);
  494.       Index := 0; UpdateLineNum(Index);
  495.     END;  (* IF Description[Index].Size = DirSize *)
  496.    END;
  497.  END;  (* DirDown *)
  498.  
  499.  PROCEDURE ReSortDirectory;
  500.  
  501.  BEGIN
  502.   ReSortFileList; ReverseFlag := FALSE;
  503.   DrawStatusLine(FALSE,(CutPasteDesc <> ''),Changed, ReverseFlag);
  504.  
  505.   StartIndex := 0; Index := 0;
  506.   RedrawScreen; UpdateLineNum(Index);
  507.  END; (* ReSortDirectory *)
  508.  
  509.  FUNCTION IsADelimiter(c: CHAR): BOOLEAN;
  510.  (* used by Ctrl-Left resp Ctrl-Right to recognize the end of a word *)
  511.  
  512.  BEGIN
  513.   IsADelimiter := (Pos(c,DelimiterTable) > 0);
  514.  END;
  515.  
  516. BEGIN  (* EditDescriptions *)
  517.  Index := 0; UpdateLineNum(Index);
  518.  
  519.  ResetCursor(Overwrite);
  520.  EditStr := ActFileData^.GetDesc;
  521.  ReverseFlag := FALSE; InShiftState := FALSE; x := 1;
  522.  REPEAT
  523.   REPEAT
  524.     Key := $0000;
  525.     IF KeyPressed THEN Key := GetKey
  526.     ELSE
  527.       BEGIN
  528.         IF MouseLoaded THEN
  529.           BEGIN
  530.             MouseMotion;
  531.             IF VMickey > VMickeysPerKeyPress THEN Key := kbDown
  532.             ELSE
  533.               IF VMickey < -VMickeysPerKeyPress THEN Key := kbUp
  534.               ELSE
  535.               IF HMickey >  HMickeysPerKeyPress THEN Key := kbRight
  536.               ELSE
  537.                 IF HMickey < -HMickeysPerKeyPress THEN Key := kbLeft
  538.                 ELSE
  539.                   BEGIN
  540.                     ButtonReleased(Left);
  541.                     IF ReleaseCount > 0 THEN Key := kbEnter;
  542.                     ButtonReleased(Right);
  543.                     IF ReleaseCount > 0 THEN Key := kbEsc;
  544.                   END;
  545.  
  546.           END;  (* if mouseloaded *)
  547.       END;
  548.   UNTIL Key <> $0000;
  549.  
  550.   IF NOT InShiftState THEN ox := x;
  551.   (* save the old cursor position for cutting *)
  552.   CASE Key OF
  553.    kbUp       : BEGIN
  554.                  ActFileData^.AssignDesc(EditStr);
  555.                  x := 1;
  556.                  DisplayFileEntry(Index,0,x,FALSE,FALSE); PrevIndex(Index);
  557.                  InShiftState := FALSE;
  558.                 END; (* Up *)
  559.  
  560.    kbDown     : BEGIN
  561.                  ActFileData^.AssignDesc(EditStr);
  562.                  x := 1;
  563.                  DisplayFileEntry(Index,0,x,FALSE,FALSE); NextIndex(Index);
  564.                  InShiftState := FALSE;
  565.                 END; (* Down *)
  566.  
  567.    kbLeft     : BEGIN
  568.                  x := Max(1,x-1);
  569.                  InShiftState := (GetShiftState AND (kbRightShift+kbLeftShift) <> 0);
  570.                 END; (* Left *)
  571.  
  572.    kbRight    : BEGIN
  573.                  x := Max(1,Min(1+x,Length(EditStr)+1));
  574.                  InShiftState := (GetShiftState AND (kbRightShift+kbLeftShift) <> 0);
  575.                 END; (* Right *)
  576.  
  577.    kbCtrlLeft : BEGIN
  578.                  DEC(x);
  579.                  WHILE (x > 0) AND IsADelimiter(EditStr[x]) DO DEC(x);
  580.                  WHILE (x > 0) AND NOT IsADelimiter(EditStr[x]) DO DEC(x);
  581.                  INC(x);
  582.                  InShiftState := (GetShiftState AND (kbRightShift+kbLeftShift) <> 0);
  583.                 END; (* ^Left *)
  584.  
  585.    kbCtrlRight: BEGIN
  586.                  l := Length(EditStr);
  587.                  WHILE (x < l) AND NOT IsADelimiter(EditStr[x]) DO INC(x);
  588.                  WHILE (x < l) AND IsADelimiter(EditStr[x]) DO INC(x);
  589.                  IF x = l THEN INC(x);
  590.                  InShiftState := (GetShiftState AND (kbRightShift+kbLeftShift) <> 0);
  591.                 END; (*  ^Right *)
  592.  
  593.    kbHome     : BEGIN
  594.                  x := 1;
  595.                  InShiftState := (GetShiftState AND (kbRightShift+kbLeftShift) <> 0);
  596.                 END; (* Home *)
  597.  
  598.    kbEnd      : BEGIN
  599.                   x := Min(Length(EditStr)+1,MaxDescLen);
  600.                   InShiftState := (GetShiftState AND (kbRightShift+kbLeftShift) <> 0);
  601.                 END; (* End *)
  602.  
  603.    kbCtrlHome : BEGIN
  604.                  Delete(EditStr,1,x);
  605.                  ActFileData^.AssignDesc(EditStr);
  606.                  x := 1;
  607.                  Changed := TRUE; InShiftState := FALSE;
  608.                 END;  (* ^Home *)
  609.  
  610.    kbCtrlEnd  : BEGIN
  611.                  Delete(EditStr,x,MaxDescLen);
  612.                  ActFileData^.AssignDesc(EditStr);
  613.                  Changed := TRUE; InShiftState := FALSE;
  614.                 END;  (* ^End *)
  615.  
  616.    kbIns      : BEGIN
  617.                  IF GetShiftState AND kbCtrlShift = kbCtrlShift THEN (* ^Ins: Copy *)
  618.                   BEGIN
  619.                    CutPasteDesc := Copy(EditStr,ox,x-ox);
  620.                    Changed := TRUE;
  621.                   END
  622.                  ELSE IF GetShiftState AND (kbRightShift+kbLeftShift) <> 0 THEN (* Shift-Ins: Paste *)
  623.                   BEGIN
  624.                    IF CutPasteDesc > '' THEN
  625.                     BEGIN
  626.                      EditStr := Copy(EditStr,1,x-1)+CutPasteDesc+Copy(EditStr,x,255);
  627.                      ActFileData^.AssignDesc(EditStr);
  628.                      Changed := TRUE;
  629.                      IF PasteMovesToNextIndex THEN
  630.                       BEGIN
  631.                        DisplayFileEntry(Index,0,x,FALSE,FALSE);
  632.                        NextIndex(Index);
  633.                       END;
  634.                     END
  635.                   END
  636.                  ELSE
  637.                   BEGIN
  638.                    Overwrite := NOT Overwrite; ResetCursor(Overwrite);
  639.                   END;
  640.                 END; (* Ins *)
  641.  
  642.    kbDel      : BEGIN
  643.                  IF GetShiftState AND kbCtrlShift = kbCtrlShift THEN (* ^Del: Clear *)
  644.                   BEGIN
  645.                    System.Delete(EditStr,ox,x-ox); x := ox;
  646.                    ActFileData^.AssignDesc(EditStr);
  647.                    Changed := TRUE; InShiftState := FALSE;
  648.                    DisplayFileEntry(Index,0,x,FALSE,FALSE);
  649.                   END
  650.                  ELSE IF GetShiftState AND (kbRightShift+kbLeftShift) <> 0 THEN (* Shift-Del: Cut *)
  651.                   BEGIN
  652.                    CutPasteDesc := Copy(EditStr,ox,x-ox);
  653.                    Delete(EditStr,ox,x-ox); x := ox;
  654.                    ActFileData^.AssignDesc(EditStr);
  655.                    Changed := TRUE; InShiftState := FALSE;
  656.                    DisplayFileEntry(Index,0,x,FALSE,FALSE);
  657.                   END
  658.                  ELSE
  659.                   BEGIN
  660.                    IF x <= Length(EditStr) THEN Delete(EditStr,x,1);
  661.                    ActFileData^.AssignDesc(EditStr);
  662.                    Changed := TRUE;
  663.                   END;
  664.                 END; (* Del *)
  665.  
  666.    kbBack     : BEGIN
  667.                  Delete(EditStr,x-1,1);
  668.                  ActFileData^.AssignDesc(EditStr);
  669.                  IF x > 1 THEN
  670.                   BEGIN
  671.                    DEC(x);
  672.                    IF x > Length(EditStr) THEN x := Length(EditStr)+1;
  673.                   END;
  674.                  Changed := TRUE; InShiftState := FALSE;
  675.                 END; (* Backspace *)
  676.  
  677.    kbPgUp     : BEGIN
  678.                  ActFileData^.AssignDesc(EditStr);
  679.                  x := 1;
  680.                  DisplayFileEntry(Index,0,x,FALSE,FALSE);
  681.                  Index := Max(Index-ScreenSize,0);
  682.                  StartIndex := Index;
  683.                  RedrawScreen;
  684.                  UpdateLineNum(Index);
  685.                  InShiftState := FALSE;
  686.                 END; (* PgUp *)
  687.  
  688.    kbPgDn     : BEGIN
  689.                  ActFileData^.AssignDesc(EditStr);
  690.                  Index := Min(Index+ScreenSize,FileList^.Count-1);
  691.                  StartIndex := Max(Index-ScreenSize,0);
  692.                  x := 1;
  693.                  DisplayFileEntry(Index,0,x,FALSE,FALSE);
  694.                  RedrawScreen;
  695.                  UpdateLineNum(Index);
  696.                  InShiftState := FALSE;
  697.                 END; (* PgDn *)
  698.  
  699.    kbCtrlPgUp : BEGIN
  700.                  ActFileData^.AssignDesc(EditStr);
  701.                  x := 1;
  702.                  DisplayFileEntry(Index,0,x,FALSE,FALSE);
  703.                  StartIndex := 0; Index := 0;
  704.                  RedrawScreen;
  705.                  UpdateLineNum(Index);
  706.  
  707.                  ActFileData^.AssignDesc(EditStr);
  708.                  DisplayFileEntry(Index,0,x,FALSE,FALSE);
  709.                  IF Length(ActDir) > 3 THEN NextIndex(Index);
  710.                  InShiftState := FALSE;
  711.                 END; (* ^PgUp *)
  712.  
  713.    kbCtrlPgDn : BEGIN
  714.                  ActFileData^.AssignDesc(EditStr);
  715.                  x := 1;
  716.                  DisplayFileEntry(Index,0,x,FALSE,FALSE);
  717.                  StartIndex := Max(FileList^.Count-ScreenSize-1,0);
  718.                  Index := FileList^.Count-1;
  719.                  RedrawScreen;
  720.                  UpdateLineNum(Index);
  721.                  InShiftState := FALSE;
  722.                 END; (* ^PgDn *)
  723.  
  724.    kbAltD     : BEGIN
  725.                  EditStr := ''; ActFileData^.AssignDesc('');
  726.                  Changed := TRUE; InShiftState := FALSE;
  727.                  x := 1;
  728.                  IF PasteMovesToNextIndex THEN
  729.                   BEGIN
  730.                    DisplayFileEntry(Index,0,x,FALSE,FALSE);
  731.                    NextIndex(Index);
  732.                   END;
  733.                 END; (* Alt-D *)
  734.  
  735.    kbAltM,
  736.    kbAltT     : BEGIN
  737.                  CutPasteDesc := ActFileData^.GetDesc;
  738.                  ActFileData^.AssignDesc(''); EditStr := '';
  739.                  Changed := TRUE; InShiftState := FALSE;
  740.                  x := 1;
  741.                  IF PasteMovesToNextIndex THEN
  742.                   BEGIN
  743.                    DisplayFileEntry(Index,0,x,FALSE,FALSE);
  744.                    NextIndex(Index);
  745.                   END;
  746.                 END; (* Alt-M / Alt-T *)
  747.  
  748.    kbAltC     : BEGIN
  749.                  CutPasteDesc := ActFileData^.GetDesc;
  750.                  x := 1;
  751.                  InShiftState := FALSE;
  752.                  DrawStatusLine(TRUE,(CutPasteDesc <> ''),Changed, ReverseFlag);
  753.                 END; (* Alt-C *)
  754.  
  755.    kbAltP     : BEGIN
  756.                  IF CutPasteDesc > '' THEN
  757.                   BEGIN
  758.                    EditStr := CutPasteDesc; ActFileData^.AssignDesc(EditStr);
  759.                    Changed := TRUE; InShiftState := FALSE;
  760.                    IF PasteMovesToNextIndex THEN
  761.                     BEGIN
  762.                      DisplayFileEntry(Index,0,x,FALSE,FALSE);
  763.                      NextIndex(Index);
  764.                     END;
  765.                   END
  766.                 END;
  767.  
  768.    kbEnter    : BEGIN
  769.                   ActFileData^.AssignDesc(EditStr);
  770.                   x := 1;
  771.                   IF (Index < FileList^.Count) THEN
  772.                     BEGIN
  773.                       n  := ActFileData^.Name+ActFileData^.Ext;
  774.                       IF ActFileData^.IsADir THEN
  775.                         IF (n[1] = '.') AND (n[2] = '.') THEN DirUp
  776.                           ELSE
  777.                         IF n[1] <> '.' THEN DirDown;
  778.                     END;
  779.                 END; (* Enter = go into directory where the cursor is at *)
  780.  
  781.    kbF1       : BEGIN                                   (* F1: Help *)
  782.                  ShowHelpPage;
  783.                  ResetCursor(Overwrite);
  784.                  DrawMainScreen(Index,FileList^.Count,x,Length(EditStr));
  785.                  DrawDirLine(FALSE);
  786.                  RedrawScreen;
  787.                  UpdateLineNum(Index);
  788.                 END;  (* F1 *)
  789.  
  790.    kbF4       : DirDown; (* F4 *)
  791.    kbF5       : DirUp;   (* F5 *)
  792.  
  793.    kbAltL,
  794.    kbF6       : BEGIN                                   (* F6: Change Drive *)
  795.                  IF Changed THEN QuerySaveDescriptions;
  796.  
  797.                  ASM
  798.                   mov ah,0eh       (* Select Disk *)
  799.                   mov dl,3
  800.                   int 21h
  801.                   add al,'@'
  802.                   mov LastDrv,al
  803.                  END;
  804.  
  805.                  IF LastDrv > 'Z' THEN LastDrv := 'Z';
  806.  
  807.                  TextColor(StatusFg); TextBackGround(StatusBg); Drv := ' :';
  808.                  GotoXY(1,MaxLines);
  809.                  Write(Chars(' ',((ScreenWidth-24) div 2)),
  810.                       'New drive letter (A..',LastDrv,'): ');
  811.                  ClrEol;
  812.                  REPEAT
  813.                   Drv[1] := UpCase(ReadKey);
  814.                  UNTIL (Drv[1] >= 'A') AND (Drv[1] <= LastDrv);
  815.                  IF Drv[1] <= 'B' THEN Drv := Drv + '\';
  816.                  OldDir := ActDir;
  817.                  {$I-}
  818.                  ChDir(Drv);
  819.                  {$I+}
  820.                  IF IOResult = 0 THEN
  821.                   BEGIN
  822.                    GetDir(0,ActDir); IORes := IOResult;
  823.                    ReadFiles;
  824.                    IF FileList^.Count = 0 THEN
  825.                     BEGIN
  826.                      IF (Length(OldDir) > 3) AND (OldDir[Length(OldDir)] = '\') THEN
  827.                         Delete(OldDir,Length(OldDir),1);
  828.                      {$I-}
  829.                      ChDir(OldDir); IORes := IOResult;
  830.                      {$I+}
  831.                      ReportError('There are no files on drive '+Drv+'. Press any key.',(CutPasteDesc <> ''),Changed);
  832.                      ReadFiles;
  833.                     END;
  834.                    RedrawScreen;
  835.                    Index := 0;
  836.                    UpdateLineNum(Index);
  837.                   END
  838.                  ELSE
  839.                   ReportError('Drive '+Drv+' not ready! Drive remains unchanged, press a key.',(CutPasteDesc <> ''),Changed);
  840.                 END;  (* Alt-L or F6 *)
  841.  
  842.    kbF2,
  843.    kbF10     : BEGIN                                    (* F2, F10: Save *)
  844.                 SaveDescriptions;
  845.                 UpdateLineNum(Index);
  846.                END; (* F10 or F2 *)
  847.    kbAltS,
  848.    kbShiftF10: BEGIN                                    (* Shell to [4]DOS *)
  849.                 IF Changed THEN QuerySaveDescriptions;
  850.  
  851.                 DoneMemory;
  852.                 SetMemTop(HeapPtr);
  853.  
  854.                 NormVideo; ClrScr;
  855.                 WriteLn('Type `Exit'' to return to 4DESC.');
  856.                 SwapVectors;
  857.                 Exec(GetEnv('COMSPEC'),'');
  858.                 SwapVectors;
  859.  
  860.                 SetMemTop(HeapEnd);
  861.                 InitMemory;
  862.  
  863.                 IF MouseLoaded THEN MouseReset;
  864.  
  865.                 ClrScr;
  866.                 DrawMainScreen(Index,FileList^.Count,x,Length(EditStr));
  867.                 DrawStatusLine(TRUE,(CutPasteDesc <> ''),Changed, ReverseFlag);
  868.                 DrawDirLine(TRUE);
  869.                 IF DosError > 0 THEN
  870.                   ReportError('Can''t load command interpreter / program execution failed.',
  871.                              (CutPasteDesc <> ''),Changed);;
  872.                 ReadFiles;
  873.                 RedrawScreen;
  874.                 UpdateLineNum(Index);
  875.                 ResetCursor(Overwrite);
  876.                END; (* Alt-S or F10 *)
  877.  
  878.    kbF3,                                                (* F3, Alt-V: View File *)
  879.    kbAltV,                                              (* Alt-E: Edit File *)
  880.    kbAltE      : IF (Index < FileList^.Count) THEN
  881.                 BEGIN
  882.                  IF NOT ActFileData^.IsADir THEN
  883.                   BEGIN
  884.                    NewName := ActFileData^.Name;
  885.                    StripTrailingSpaces(NewName);
  886.                    NewName := NewName+ActFileData^.Ext;
  887.                    NewDir := ActDir; (* I do not want to loose actdir, newdir
  888.                                         is only a "dummy" variable. *)
  889.                    IF NewDir[Length(NewDir)] = '\' THEN Delete(NewDir,Length(NewDir),1);
  890.  
  891.                    DoneMemory;
  892.                    SetMemTop(HeapPtr);
  893.                    SwapVectors;
  894.  
  895.                    NormVideo; ClrScr;
  896.  
  897.                    IF Key = kbAltE THEN
  898.                     Exec(GetEnv('COMSPEC'),'/c '+EditCmd+' '+NewDir+'\'+NewName)
  899.                    ELSE
  900.                     Exec(GetEnv('COMSPEC'),'/c '+ListCmd+' '+NewDir+'\'+NewName);
  901.  
  902.                    SwapVectors;
  903.                    SetMemTop(HeapEnd);
  904.                    InitMemory;
  905.  
  906.                    IF MouseLoaded THEN MouseReset;
  907.  
  908.                    ClrScr;
  909.                    DrawMainScreen(Index,FileList^.Count,x,Length(EditStr));
  910.                    DrawStatusLine(TRUE,(CutPasteDesc <> ''),Changed, ReverseFlag);
  911.                    DrawDirLine(FALSE);
  912.                    IF DosError > 0 THEN ReportError('Can''t load command interpreter/program execution failed.',
  913.                                                    (CutPasteDesc <> ''),Changed);
  914.                    RedrawScreen;
  915.                    UpdateLineNum(Index);
  916.                    ResetCursor(Overwrite);
  917.                  END;
  918.                 END; (* F3, Alt-V, or Alt-E *)
  919.    (* Sorting Options *)
  920.    Ord('R')-64  : BEGIN
  921.                    ReverseFlag := NOT ReverseFlag;
  922.                    DrawStatusLine(FALSE,(CutPasteDesc <> ''),Changed, ReverseFlag);
  923.                   END;
  924.    Ord('N')-64  : BEGIN
  925.                    IF NOT ReverseFlag THEN SortKey := SortByName
  926.                                       ELSE SortKey := SortByNameRev;
  927.                    ReSortDirectory;
  928.                   END;
  929.    Ord('E')-64  : BEGIN
  930.                    IF NOT ReverseFlag THEN SortKey := SortByExt
  931.                                       ELSE SortKey := SortByExtRev;
  932.                    ReSortDirectory;
  933.                   END;
  934.    Ord('S')-64  : BEGIN
  935.                    IF NOT ReverseFlag THEN SortKey := SortBySize
  936.                                       ELSE SortKey := SortBySizeRev;
  937.                    ReSortDirectory;
  938.                   END;
  939.    Ord('D')-64  : BEGIN
  940.                    IF NOT ReverseFlag THEN SortKey := SortByDate
  941.                                       ELSE SortKey := SortByDateRev;
  942.                    ReSortDirectory;
  943.                   END;
  944.   ELSE
  945.    IF (Ord(Key) > 31) AND (Ord(Key) < 256) THEN
  946.     BEGIN
  947.      IF NOT Changed THEN Changed := TRUE;
  948.      ReverseFlag := FALSE; InShiftState := FALSE;
  949.  
  950.      IF x <= MaxDescLen THEN
  951.       BEGIN
  952.        IF Overwrite AND (x <= Length(EditStr)) THEN
  953.          EditStr[x] := Chr(Key)
  954.        ELSE
  955.          EditStr := Copy(EditStr,1,x-1)+Chr(Key)+Copy(EditStr,x,255);
  956.  
  957.        ActFileData^.AssignDesc(EditStr);
  958.        INC(x); UpdateColNum(x,Length(EditStr));
  959.       END;
  960.     END; (* all others *)
  961.   END;   (* case *)
  962.  
  963.   (* Select with the Shift Keys *)
  964.   IF InShiftState THEN CutPasteDesc := Copy(EditStr,ox,x-ox);
  965.  
  966.   IF Changed THEN
  967.    DrawStatusLine(TRUE,(CutPasteDesc <> ''),Changed, ReverseFlag);
  968.   DisplayFileEntry(Index,ox,x,InShiftState,TRUE);
  969.   UpdateColNum(x,Length(EditStr));
  970.  UNTIL (Key = kbEsc)  OR  (* Esc   = exit to original directory and save *)
  971.        (Key = kbAltX) OR  (* Alt-X = exit to current  directory and save *)
  972.        (Key = kbAltQ);    (* Alt-Q = exit to original directory, don't save *)
  973.  
  974.  IF (Key = kbEsc) OR (Key = kbAltQ) THEN ResetDir := TRUE
  975.                                     ELSE ResetDir := FALSE;
  976.  
  977.  IF Changed AND (Key <> kbAltQ) THEN QuerySaveDescriptions;
  978. END; (* EditDescriptions *)
  979.  
  980. (*-------------------------------------------------------- Main *)
  981. BEGIN
  982.  {$I-}
  983.  GetDir(0,StartDir); IORes  := IOResult;
  984.  {$I+}
  985.  ShowHelp := FALSE; Querier := TRUE;
  986.  IF ParamCount > 0 THEN
  987.   BEGIN
  988.    FOR i := 1 TO Min(2,ParamCount) DO
  989.     BEGIN
  990.      FirstParam := ParamStr(i);
  991.      IF (FirstParam[1] = '/') OR (FirstParam[1] = '-') THEN
  992.       BEGIN
  993.        IF NOT Monochrome THEN Monochrome := (UpCase(FirstParam[2]) = 'M');
  994.        IF     Querier    THEN Querier    := NOT (UpStr(Copy(FirstParam,2,Length(FirstParam)-1)) = 'DONTASK');
  995.        IF NOT ShowHelp   THEN ShowHelp   := (UpCase(FirstParam[2]) = 'H') OR
  996.                                             (FirstParam[2] = '?');
  997.       END;
  998.     END;  (* for ... do begin *)
  999.    NewDir := UpStr(ParamStr(ParamCount));
  1000.    IF (NewDir[1] <> '/') AND (NewDir[1] <> '-') THEN
  1001.     BEGIN
  1002.     {$I-}
  1003.     ChDir(NewDir); IORes := IOResult;
  1004.     {$I+}
  1005.     END;
  1006.   END;  (* if paramcount > 0 *)
  1007.  
  1008.  (* Read the .INI files *)
  1009.  InitMemory;
  1010.  
  1011.  INIStrings := New(PINIStrings,Init); (* Read in the .INI file(s) *)
  1012.  
  1013.  IF INIFileExists THEN StringDateHandling.EvaluateINIFileSettings;
  1014.  (* The Date & Time Formats are country-specific and are pre-initialized
  1015.     in the StringDateHandling initialize-section. Re-Initializing it
  1016.     with "our" defaults is not what the users wants.                     *)
  1017.  
  1018.  DescriptionHandling.EvaluateINIFileSettings;
  1019.  DisplayKeyboardAndCursor.EvaluateINIFileSettings;
  1020.  ChooseColors(Monochrome);
  1021.  
  1022.  dmouse.EvaluateINIFileSettings;
  1023.  IF UseMouse THEN MouseReset;
  1024.  
  1025.  DelimiterTable := ReadSettingsString('misc','delimiters',DelimiterTable);
  1026.  DelimiterTable := ' '+DelimiterTable;
  1027.  
  1028.  PasteMovesToNextIndex :=  (ReadSettingsChar('misc','pastemovestonextindex','y') = 'y');
  1029.  
  1030.  overwrite := (ReadSettingsString('','editmode','overstrike') = 'overstrike');
  1031.  
  1032.  Dispose(INIStrings,Done); INIStrings := NIL;
  1033.  
  1034.  EdStart := 25+Length(DateFormat)+Length(TimeFormat);
  1035.  DispLen := ScreenWidth-EdStart;
  1036.  
  1037.  Str(DispLen,s); Template:= '%-12s %s %s %s %-'+s+'s';
  1038.  Changed := FALSE; CutPasteDesc := '';
  1039.  
  1040.  DrawMainScreen(0,0,0,0);
  1041.  IF ShowHelp THEN ShowHelpPage;
  1042.  IF IORes > 0 THEN
  1043.   ReportError(NewDir+' not found. Directory remains unchanged.',FALSE,FALSE);
  1044.  
  1045.  ReadFiles;
  1046.  IF DosError = 0 THEN
  1047.   BEGIN
  1048.    RedrawScreen;
  1049.    EditDescriptions;
  1050.   END
  1051.  ELSE
  1052.   BEGIN
  1053.    ReportError('Drive '+NewDir+' not ready, exiting (key).',FALSE,FALSE);
  1054.    ResetDir := TRUE;
  1055.   END;
  1056.  
  1057.  Dispose(FileList,Done); FileList := NIL;
  1058.  DoneMemory;
  1059.  
  1060.  IF ResetDir THEN
  1061.    BEGIN
  1062.      {$I-}
  1063.      ChDir(StartDir);
  1064.      IORes := IOResult;
  1065.      {$I+}
  1066.    END;
  1067.  
  1068.  IF MouseLoaded THEN MouseReset;
  1069.  SetCursorShape(OrigCursor);
  1070.  NormVideo;
  1071.  ClrScr;
  1072.  WriteLn(Header1);
  1073.  WriteLn(Header2);
  1074.  WriteLn;
  1075.  WriteLn('This program is freeware: you are allowed to use, copy it free');
  1076.  WriteLn('of charge, but you may not sell or hire 4DESC.');
  1077. END.
  1078.